package scalab.tasks.compilers

import scalab.core.component.Task

/**
* Allows to compile scala code with the resident scala compiler.
*/

trait Scalac extends JVMCompiler{

	import scala.tools.nsc.Settings
	import scala.tools.nsc.reporters.ConsoleReporter
	import scala.tools.nsc.{Global, CompilerCommand}
	import java.io.File

	val args: Iterable[String]
 	 
	val settings = new Settings(error)
	var global: Global = _

	var outDir: String = _
	 
	//give colors?
	private lazy val reporter = new ConsoleReporter(settings)

	def execute(destinationDir: String): Boolean = {
		info("destDir: " + destinationDir)
		val res = compile(destinationDir, srcs.sources.toList map {_.getPath})
		val (errors, warnings) = (res >> 16, res & 0x00FF)
		if(errors > 0){
			error("Compilation failed with " + errors + " error" + (if (errors > 1) "s" else "") + ".")
			false
		}
		else{
			built(getCompiledFiles)
			if (warnings > 0){
				warning("Compilation suceeded with " + warnings + " warning" + (if (warnings > 1) "s" else "") + ".")
			}
			else{
				info("Compilation Succeeded!")
			}
			true
		}	
	}
	 
	def compile(destinationDir: String, files: List[String]): Int = {
		outDir = destinationDir
		val command = new CompilerCommand(args.toList, settings, error, false)
		if(!settings.outdir.isDefault){
			warning("Option '" + settings.outdir.name + "' with value '" + settings.outdir.value + 
				"' will be overriden to '" + destinationDir + "'!\nTo avoid this message appearance, " +
				"unset this option.")
		}
		settings.outdir.tryToSet(List("-d", destinationDir))
		settings.classpath.tryToSet(List("-classpath", (classpath.sources map {d => d.canonicalPath}).mkString("",":","")))//: => File.pathSeparator
		val nsc = new Global(settings, reporter)
		global = nsc
		val run = new nsc.Run
		run compile (files)
		reporter.ERROR.count << 16 | reporter.WARNING.count
	}

	def getCompiledFiles: Iterable[String] = {
		import scala.tools.nsc.backend.jvm._
		val genJVM = global.genJVM
		val jvmPhase = genJVM.global.currentRun.phaseNamed("jvm").asInstanceOf[genJVM.JvmPhase]
		val generator = jvmPhase.codeGenerator
		genJVM.global.icodes.classes.values.toList flatMap {iclass => 
			var lst: List[String] = Nil
			lst = inDestinationDir(generator.javaName(iclass.symbol)) + ".class" :: lst 
			val hasSerialVUID = iclass.symbol.attributes exists {a => a match{
				case genJVM.global.AnnotationInfo(tp, value :: _, _) if tp.typeSymbol == generator.SerialVersionUID => true
				case _ => false
			}}

			if ((generator.isStaticModule(iclass.symbol) || hasSerialVUID) && 
				generator.isTopLevelModule(iclass.symbol) && (
				iclass.symbol.linkedClassOfModule == genJVM.global.NoSymbol || 
					!genJVM.global.currentRun.compiles(iclass.symbol.linkedClassOfModule)
				)){

				val moduleName = generator.javaName(iclass.symbol) // + "$"
				val mirrorName = moduleName.substring(0, moduleName.length - 1) + ".class"
				lst = inDestinationDir(mirrorName) :: lst
			}

			if (iclass.symbol.attributes.exists(_.atp.typeSymbol == generator.BeanInfoAttr))
				lst = inDestinationDir(generator.javaName(iclass.symbol) + "BeanInfo.class") :: lst
			lst
		}
	}

	def inDestinationDir(path: String): String = 
		outDir + path

	def instanceId: String = ""//calculate according to compiler's parameters

	//this is a big hack because graph is modified during execution
	def setClasspath(paths: Iterable[String]){
		import scalab.core.component.{Universe, Target}
		import scalab.filters.selectors.Files
		val fs = Files(paths)
		Universe -> fs 
		val droper = fs.discard({d => false})
		fs >> (droper, classpath)
		val target = Target(".", droper) //no depende
		target.update
	}
}

object Scalac{

	def apply(compilerName: String): Scalac = apply(compilerName, Array())

	def apply(compilerName: String, options: Iterable[String]): Scalac = new Scalac{
		val name: String = compilerName
		val args: Iterable[String] = options
		val classpath = optionalIn
		val bootClasspath = optionalIn
	}

	def apply(options: Iterable[String]): Scalac = apply("Scalac", options)

	def apply(): Scalac = apply(Array[String]())
}
